Skip to main content

Custom drawings

For source code owners only!

How to create and add a custom drawing

DXcharts includes a wide range of built-in drawings. If you're maintaining the DXcharts codebase and need to implement a custom drawing, this guide walks you through the process of creating and registering one step by step.

🔸 AbstractFigure

All drawings extend the AbstractFigure class:

export abstract class AbstractFigure<T> {
// number of points being used in the process of drawing creation
abstract points: number;
public data: unknown;
// type of drawing, usually each drawing has it's own type
abstract readonly type: DrawingType;
// hide the drawing
hidden?: StringTMap<number>;
// add drawing after clicking 'Enter' button
public readonly commitOnEnter: boolean = false;
// additional vector to drawings which contain text (TextNote, Callout)
textToolPoint?: Vector;
// edit mode turns off text container to drawings which contain text (TextNote, Callout)
isEditing?: boolean;
// update props when drawing is hidden and can't be reached
updateHiddenProps?: (points: Vector[]) => void;
// returns labels of Y axis
getYAxisLabels?: (
viewModel: DrawingViewModel,
ctx: CanvasRenderingContext2D,
chartBootstrap: ChartWithDrawings,
) => YAxisLabelDrawProps[];
// returns labels of X axis
getXAxisLabels?: (
chart: ChartWithDrawings,
viewModel: DrawingViewModel,
properties: CommonFigureProps,
points: Vector[],
) => Array<XAxisLabel>;
constraint?: FigureConstraint;
// returns key viewpoints values
calculateKeyViewPoints?: (points: ViewPoint[]) => ViewPoint[];
resize?: (viewModel: DrawingViewModel, movedPoint: PointEvent) => void;
rotate?: (viewModel: DrawingViewModel, movedPoint: PointEvent, match: DragDrawingInfo) => void;
borderMatchFunction?: (
viewModel: DrawingViewModel,
initialPoint: IndexValuePoint,
chartBootstrap: ChartWithDrawings,
) => boolean;
// setup some custom action when figure is being moved
customMoveFigureAction?: (
viewModel: DrawingViewModel,
chartBootstrap: ChartWithDrawings,
drawings: DrawingsModel,
initial: IndexValuePoint,
initialValues: IndexValuePoint[],
point: PointEvent,
) => void;
wheelFigureAction?: (increment: number) => void;
// setup some custom action when mouse up event is triggered for current figure
onUp?: (viewModel: DrawingViewModel, drawings: DrawingsModel) => void;
disablePointDrag?: boolean;
showPoints?: (viewModel: DrawingViewModel) => boolean;
// return value indicates whether we should commit figure or not
beforeCommit?: (model: DrawingViewModel, drawings: DrawingsModel) => boolean;
// return value indicates whether we should cancel figure or not
beforeCancel?: (model: DrawingModel<DrawingType>, drawings: DrawingsModel) => boolean;
// used in drawing mode to start a new drawing after the old one is finished
getClone(model: DrawingModel<DrawingType>): NullableDrawingModel {
return cloneUnsafe(model);
}
pointConstraint?: (point: PointEvent, viewModel: DrawingViewModel, chartBootstrap: ChartWithDrawings) => boolean;
downMoveHandler?: (point: PointEvent, viewModel: DrawingViewModel, drawings: DrawingsModel) => void;
isDisallowed?: (model: DrawingModel<DrawingType>, pointEvent: PointEvent) => boolean;
// set a cursor appearance
getCursor?: (point: ViewPoint, viewModel: DrawingViewModel, chart: ChartWithDrawings) => CursorType | undefined;
// implementation of drawing logic
abstract draw(
viewModel: DrawingViewModel,
ctx: ExtendedContext,
properties: T,
chartBootstrap: ChartWithDrawings,
paneComponent: PaneComponent,
drawings: DrawingsModel,
): void;
drawPoints?(
vm: DrawingViewModel,
ctx: ExtendedContext,
chartConfig: FullChartConfigDrawings,
drawings: DrawingsModel,
radiusExtension: number,
): void;
toString() {
return this.type;
}
}

You must implement the following:

  • points
  • DrawingType
  • draw method

These are the core properties required for any custom drawing.

Creating a drawing

DXcharts already includes geometric shapes like Oval and Rectangle. In this example, you'll create a custom Triangle.

Start by creating a file that extends the AbstractFigure class. By convention, these files are located in chart-core-modules/modules/drawings/figures/ folder, but you can choose any location.

export class Triangle extends AbstractFigure<TriangleProperties> {
public points: number = 3;
//@ts-ignore triangle drawing type is not implemented by default
public type: DrawingType = 'triangle';
draw(viewModel: DrawingViewModel, ctx: ExtendedContext, properties: TriangleProperties): void {
const points = viewModel.keyViewPoints;
const lineProperties = getLineProperties(viewModel.model, properties);
if (points.length === 2) {
ctx.prepareStroke(lineProperties);
ctx.beginPath();
// TODO fix TS error
// @ts-ignore
ctx.addSegmentPath(points[0], points[1]);
ctx.stroke();
}
if (points.length === 3) {
if (getProperty(properties.style, canvasPropertiesMap.PROP_FILL_BACKGROUND, true)) {
ctx.prepareFill(lineProperties);
ctx.beginPath();
// TODO fix TS error
// @ts-ignore
this.addTrianglePath(points[0], points[1], points[2], ctx);
ctx.fill();
}
ctx.prepareStroke(lineProperties);
ctx.beginPath();
// TODO fix TS error
// @ts-ignore
this.addTrianglePath(points[0], points[1], points[2], ctx);
ctx.stroke();
}
}
addTrianglePath = (a: Vector, b: Vector, c: Vector, context: ExtendedContext): void => {
context.context.moveTo(a.x, a.y);
context.context.lineTo(b.x, b.y);
context.context.lineTo(c.x, c.y);
context.context.lineTo(a.x, a.y);
context.closePath();
};
}

Be sure to implement the points, type, and draw properties. The rest of the implementation is up to you.

🔸 Registering the drawing in the chart

After implementing the drawing, register it by updating the following files:

1. FigureFactory.ts

Import your drawing (e.g., Triangle.ts) and add a new case in the switch statement:

case 'triangle': {
return new Triangle();
}

You can optionally pass formatterProvider to the constructor if needed.

2. PropertiesByType.ts

Import the drawing and its properties interface, then update the PropertiesByType interface:

export interface TriangleProperties {
line: LineProperties;
style: {
fillBackground: boolean;
};
showTime?: boolean;
showPrice?: boolean;
}

3. DrawingTypes.ts

Add the new drawing to the availableDrawingTypes array to satisfy the TypeScript check:

export const availableDrawingTypes = [
'trend',
'line',
'horizontal_line',
'horizontal_ray',
'vertical_line',
'extended_line',
'ellipse',
'pitchfork',
'extended',
'ray',
'curve',
'arc',
'info_line',
'brush',
'path',
'date_price_range',
'date_range',
'price_range',
'highlighter',
'icon',
'rectangle',
'gann_box',
'gann_square',
'fibonacci_ark',
'fibonacci_circles',
'fibonacci_rays',
'gann_fan',
'trend_channel',
'multichannel',
'fibonacci_retracements',
'text',
'callout',
'price_label',
'base_isolation_tool',
'magnifying_tool_rectangle',
'magnifying_tool_time_range',
'magnifying_tool_time_range_wheel',
'vertical_arrow_up',
'vertical_arrow_down',
'arrow',
'elliott_wave',
'elliott_correction_wave',
'fibonacci_projection',
'fibonacci_channel',
'fibonacci_time_zones',
'regression_trend',
'fibonacci_spiral',
'cycle_brackets',
'fibonacci_time_extension',
'fibonacci_time_ratios',
'range_volume_by_price',
'anchored_volume_by_price',
] as const;

Starting a custom drawing

Use the DrawingsComponent as the entry point. Call the startDrawing method to place the first point and begin the drawing process:

export const startTriangleDrawing = (chart: ChartWithDrawings ) => {
// @ts-ignore triangle drawing type is not implemented by default
const triangleDrawingConfig: NewDrawingConfig<'triangle'> = {
properties: {
line: {
lineColor: '#FFAA00',
lineWidth: 1,
lineDash: [],
},
background: {
fillStyle: '#FFAA00',
opacity: 0.2,
},
showTime: true,
showPrice: true,
},
// @ts-ignore triangle drawing type is not implemented by default
type: 'triangle',
};
chart.drawings.startDrawing(triangleDrawingConfig);
}

Optional AbstractFigure properties

The required properties are the minimum needed to create a drawing. However, AbstractFigure also includes several optional properties and methods that can enhance behavior.

For example:

  • customEndMoveFigureAction: triggers a callback when the drawing move ends
  • getCursor: defines a custom mouse cursor

Use these options to customize interaction and improve the drawing experience.